/*
 * Copyright 2010 Dynastream Innovations Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.dsi.ant;

import java.util.Arrays;
import java.util.List;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

import com.dsi.ant.exception.*;

/**
 * AntInterface is a proxy object for communicating with the ANT Radio Service. 
 * It is recommended you have only one instance, managed from within a local 
 * long-running service (to prevent any conflicts during ANT configuration).
 * <p>
 * Detailed documentation is available in the "ANT Message Protocol and Usage"
 * document under the Developers Zone on <a href="http://www.thisisant.com">
 * http://www.thisisant.com</a>.
 * <p>
 * Missing ANT Message functions (can be implemented with {@link #ANTTxMessage(byte[]) ANTTxMessage()}):
 * <ul>
 * <li>SetNetworkKey128 (not documented in protocol doc)
 * <li>SetTransmitPower
 * <li>RxExtMesgsEnable
 * <li>LibConfig
 * <li>ConfigFrequencyAgility (only supported on some parts)
 * <li>SetChannelSearchPriority (since ANT Version AOV1.07)
 * <li>OpenRxScanMode
 * <li>SendExtBroadcastData
 * <li>SendExtAcknowledgedData
 * <li>SendExtBurstTransferPacket
 * </ul> 
 * <p>
 * Unsupported ANT Message functions (on phone ANT hardware):
 * <ul>
 * <li>SetNetworkKey
 * <li>SetSerialNumChannelId
 * <li>EnableLED
 * <li>CrystalEnable
 * <li>SetUSBDescriptorString
 * <li>Sleep
 * <li>InitCWTestMode
 * <li>SetCWTestMode
 * </ul>
 * 
 * 
 * @see 
 * <a href="http://www.thisisant.com/pages/developer-zone/ant-protocol-and-usage">
 * ANT Message Protocol and Usage</a>
 */
public class AntInterface {

    // Constants

    /** Search string to find ANT Radio Service in the Android Marketplace */
    public static final String PACKAGE_NAME_RADIO_SERVICE = "com.dsi.ant.service.socket";
    
    /** Search string to find ANT USB Service in the Android Marketplace */
    public static final String PACKAGE_NAME_USB_SERVICE = "com.dsi.ant.usbservice";
    
    /** The Log Tag. */
    public static final String TAG = "ANTInterface";

    /** Enable debug logging. */
    private static final boolean DEBUG = false;

    /** Name of the ANT Radio shared library */
    private static final String ANT_LIBRARY_NAME = "com.dsi.ant.antradio_library";
    
    /** The action string to use for binding to the ARS. Should be changed everytime a new aidl is used.*/
    private static final String INTENT_ACTION_BIND_ANT_RADIO_SERVICE = "com.dsi.ant.IAnt_6";

    /** The action string to use for discovering ANT backends when querying for ANT support. */
    private static final String INTENT_ACTION_QUERY_SERVICE_INFO = "com.dsi.ant.intent.request.SERVICE_INFO";

    // Static variables

    /** The version code (eg. 1) of ANTLib used by the ANT Radio Service */
    private static int mServiceLibraryVersionCode = 0;

    /** Make getInstance() thread-safe. */
    private static Object INSTANCE_LOCK = new Object();

    
    // Instance variables

    /** Inter-process communication with the ANT Radio Service. */
    private IAnt_6 mAntReceiver = null;

    /** The context to use for binding/unbinding the ANT Radio Service. */
    private Context mContext;

    /** Listens to changes to service connection status. */
    private ServiceListener mServiceListener;

    /** Is the ANT Radio Service connected. */
    private boolean mServiceConnected = false;

    // Interface definitions

    /**
     * An interface for notifying AntInterface clients when they have
     * been connected to the ANT Radio Service.
     */
    public interface ServiceListener 
    {
        /**
         * Called to notify the client when it has been
         * connected to the ANT Radio Service. Clients must wait for
         * this callback before making calls on the AntInterface.
         */
        public void onServiceConnected();

        /**
         * Called to notify the client that it has been
         * disconnected from the ANT Radio Service. Clients must not
         * make calls on the AntInterface until the service is
         * reconnected.
         */
        public void onServiceDisconnected();
    }


    // Constructor

    /**
     * Instantiates a new ANT Interface.
     *
     * @since 3.0
     */
    public AntInterface()
    {
        // Initialise variables on each initService
    }

    protected void finalize() throws Throwable 
    {
        try
        {
            releaseService();
        }
        finally
        {
            super.finalize();
        }
    }

    /**
     * Gets the single instance of AntInterface, creating it if it doesn't 
     * exist.
     * <p>
     * Note, since version 3.0 this will always create a new instance.
     *
     * @param context the context used to bind to the remote service.
     * @param listener the listener to be notified of status changes.
     * 
     * @return the AntInterface instance.
     * 
     * @since 1.0
     * 
     * @deprecated Use constructor to create a new instance and initService() 
     * to tell it to start connection to the ANT Radio Service.
     */
    @Deprecated
    public static AntInterface getInstance(Context context,ServiceListener listener) 
    {
        if(DEBUG)   Log.d(TAG, "getInstance");

        synchronized (INSTANCE_LOCK) 
        {
            AntInterface instance = null;

            if(DEBUG)   Log.d(TAG, "getInstance: Creating new instance");

            instance = new AntInterface();

            if(!instance.initService(context, listener))
            {
                Log.e(TAG, "getInstance: No connection to proxy service");

                instance.releaseService();
                instance = null;
            }

            return instance;
        }
    }


    
    /**
     * Requests Google Play Store application to display the ANT Radio Service.
     *
     * @param pContext the context
     *
     * @since 1.2
     */
    public static void goToMarket(Context pContext)
    {
        goToMarket(pContext, PACKAGE_NAME_RADIO_SERVICE);
    }

    /**
     * Requests Google Play Store application to display the application specified.
     *
     * @param pContext the context
     * @param packageName the application in the Google Play Store
     *
     * @since 3.2.1
     */
    public static void goToMarket(Context pContext, String packageName)
    {
        if(DEBUG) Log.d(TAG, "goToMarket for app: " + packageName);

        Intent goToMarket = null;
        goToMarket = new Intent(Intent.ACTION_VIEW,Uri.parse("http://market.android.com/details?id=" + packageName));
        goToMarket.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        pContext.startActivity(goToMarket);
    }

    /**
     * Class for monitoring the service connection.
     */
    private ServiceConnection mIAntConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName pClassName, IBinder pService) {
            // This is called when the connection with the service has been
            // established, giving us the service object we can use to
            // interact with the service.  We are communicating with our
            // service through an IDL interface, so get a client-side
            // representation of that from the raw service object.
            if(DEBUG)   Log.d(TAG, "mIAntConnection onServiceConnected()");
            mAntReceiver = IAnt_6.Stub.asInterface(pService);

            mServiceConnected = true;

            // Notify the attached application if it is registered
            if (mServiceListener != null) 
            {
                mServiceListener.onServiceConnected();
            }
            else
            {
                if(DEBUG) Log.d(TAG, "mIAntConnection onServiceConnected: No service listener registered");
            }
        }

        public void onServiceDisconnected(ComponentName pClassName) {
            // This is called when the connection with the service has been
            // unexpectedly disconnected -- that is, its process crashed.
            if(DEBUG)   Log.d(TAG, "mIAntConnection onServiceDisconnected()");

            mServiceConnected = false;
            mServiceLibraryVersionCode = 0;

            mAntReceiver = null;

            // Notify the attached application if it is registered
            if (mServiceListener != null) 
            {
                mServiceListener.onServiceDisconnected();
            }
            else
            {
                if(DEBUG) Log.d(TAG, "sIAntConnection onServiceDisconnected: No service listener registered");
            }
        }
    };

    /**
     * Binds this activity to the ANT Radio Service.
     *
     * @return true, if successful.  Will fail if already initialised.
     * 
     * @throws IllegalArgumentException if context is not provided.
     * 
     * @since 3.0
     */
    public boolean initService(Context context, ServiceListener listener) {
        if(DEBUG)   Log.d(TAG, "initService() entered");

        boolean ret = false;

        if(null == context)
        {
            throw new IllegalArgumentException("Context must be provided");
        }

        if(null != mContext)
        {
            if(DEBUG) Log.w(TAG, "initService: Already initialised, failing init.");
        }
        else
        {
        	mContext = context;
        	mServiceListener = listener;

        	ret = mContext.bindService(new Intent(INTENT_ACTION_BIND_ANT_RADIO_SERVICE), mIAntConnection, Context.BIND_AUTO_CREATE);
        	if(DEBUG) Log.i(TAG, "initService(): Bound with ANT Radio Service: " + ret);
        }

        return ret;
    }

    /** 
     * Unbinds this activity from the ANT Radio Service.
     * 
     *  @since 3.0
     */
    public void releaseService() {
        if(DEBUG)   Log.d(TAG, "releaseService() entered");

        try
        {
            if(null != mContext)
            {
                mContext.unbindService(mIAntConnection);
            }

            mServiceConnected = false;
            mServiceListener = null;
            mContext = null;

            if(DEBUG) Log.d(TAG, "releaseService(): Service unbound.");
        }
        catch(IllegalArgumentException e)
        {
        	if(DEBUG) Log.d(TAG, "releaseService(): Service already unbound.");
        }
    }


    /**
     * True if this activity can communicate with the ANT Radio Service.
     *
     * @return true, if service is connected
     * 
     * @since 1.2
     */
    public boolean isServiceConnected()
    {
        return mServiceConnected;
    }


    /**
     * Unbind from the ANT Radio Service.
     *
     * @return true, if successful
     * 
     * @since 1.0
     * 
     * @deprecated Use a single instance and start/stop connection to ANT Radio
     * Service with initService()/releaseService(). 
     */
    @Deprecated
    public boolean destroy()
    {
        if(DEBUG)   Log.d(TAG, "destroy");

        releaseService();

        return true;
    }

/////////////////////////////////////////////////////////////////////////////
//  POWER ON/OFF FUNCTIONS
/////////////////////////////////////////////////////////////////////////////

    /**
     * Enable ANT.
     *
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * 
     * @since 1.0
     */
    public void enable() throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }

        try
        {
            if(!mAntReceiver.enable())
            {
                throw new AntInterfaceException();
            }
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    }

    /**
     * Disable ANT.
     *
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * 
     * @since 1.0
     */
    public void disable() throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }

        try
        {
            if(!mAntReceiver.disable())
            {
                throw new AntInterfaceException();
            }
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    }

    /**
     * Checks if ANT is enabled.
     *
     * @return true, if is enabled.
     * 
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * 
     * @since 1.0
     */
    public boolean isEnabled() throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }

        try
        {
            return mAntReceiver.isEnabled();
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    }

/////////////////////////////////////////////////////////////////////////////
//  HELPER FUNCTIONS
/////////////////////////////////////////////////////////////////////////////
    
    /**
     * Write a raw message (8 bytes) to the ANT chip.
     * <p>
     * Only use this fucntion if the message you want has not been implemented
     * in the API.
     * <p>
     * See the 
     * <a href="http://www.thisisant.com/pages/developer-zone/ant-protocol-and-usage">
     * ANT Message Protocol and Usage</a> document for details.
     *
     * @param message the message to send.
     * 
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * 
     * @since 1.0
     */
    public void ANTTxMessage(byte[] message) throws AntInterfaceException
    {
        if(DEBUG) Log.d(TAG, "ANTTxMessage");

        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }

        try
        {
            if(!mAntReceiver.ANTTxMessage(message))
            {
                throw new AntInterfaceException();
            }
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    }

    /**
     * ANT send partial burst.
     *
     * @param channelNumber the channel number
     * @param txBuffer the data to transmit
     * @param initialPacket the initial packet
     * @param containsEndOfBurst the contains end of burst
     * 
     * @return The number of bytes still to be sent (approximately).  0 if success.
     * 
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * 
     * @since 1.0
     * 
     * @deprecated This function will be removed in a later version. Send a full burst with
     * {@link #ANTSendBurstTransfer(byte, byte[])} instead.
     */
    @Deprecated
    public int ANTSendPartialBurst(byte channelNumber, byte[] txBuffer, int initialPacket, boolean containsEndOfBurst) throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }
        
        if(!containsEndOfBurst && ((txBuffer.length % AntDefine.ANT_STANDARD_DATA_PAYLOAD_SIZE) != 0))
        {
            throw new IllegalArgumentException("Intermediate partial burst has incorrect length. An intermediate " +
                    "partial burst must be a multiple of " + AntDefine.ANT_STANDARD_DATA_PAYLOAD_SIZE + " bytes long.");
        }
        
        if(containsEndOfBurst && (initialPacket == 1) && (txBuffer.length < AntDefine.ANT_STANDARD_DATA_PAYLOAD_SIZE))
        {
            throw new IllegalArgumentException("Incorrect burst length. Bursts must be at least " + 
                    AntDefine.ANT_STANDARD_DATA_PAYLOAD_SIZE + " bytes long.");
        }

        try
        {
            return mAntReceiver.ANTTransmitBurst(channelNumber, txBuffer, initialPacket, containsEndOfBurst);
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    }
    
/////////////////////////////////////////////////////////////////////////////
//  CONFIG MESSAGES
/////////////////////////////////////////////////////////////////////////////
    
    /**
     * Unassign a channel.
     * A channel must be unassigned before it may be reassigned using the Assign Channel command.
     * <p>
     * See the 
     * <a href="http://www.thisisant.com/pages/developer-zone/ant-protocol-and-usage">
     * ANT Message Protocol and Usage</a> document for details.
     * 
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * 
     * @since 1.0
     */
    public void ANTUnassignChannel(byte channelNumber) throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }

        try
        {
            if(!mAntReceiver.ANTUnassignChannel(channelNumber))
            {
                throw new AntInterfaceException();
            }
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    }

    /**
     * Assign the a channel.
     * <p>
     * See the 
     * <a href="http://www.thisisant.com/pages/developer-zone/ant-protocol-and-usage">
     * ANT Message Protocol and Usage</a> document for details.
     * 
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * 
     * @since 1.0
     */
    public void ANTAssignChannel(byte channelNumber, byte channelType, byte networkNumber) throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }

        try
        {
            if(!mAntReceiver.ANTAssignChannel(channelNumber, channelType, networkNumber))
            {
                throw new AntInterfaceException();
            }
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    }

    /**
     * Configures the channel ID for a specific channel.
     * <p>
     * See the 
     * <a href="http://www.thisisant.com/pages/developer-zone/ant-protocol-and-usage">
     * ANT Message Protocol and Usage</a> document for details.
     * 
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * 
     * @since 1.0
     */
    public void ANTSetChannelId(byte channelNumber, short deviceNumber, byte deviceType, byte txType) throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }

        try
        {
            if(!mAntReceiver.ANTSetChannelId(channelNumber, deviceNumber, deviceType, txType))
            {
                throw new AntInterfaceException();
            }
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    }

    /**
     * Configures the messaging period of a specific channel.
     * <p>
     * See the 
     * <a href="http://www.thisisant.com/pages/developer-zone/ant-protocol-and-usage">
     * ANT Message Protocol and Usage</a> document for details.
     * 
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * 
     * @since 1.0
     */
    public void ANTSetChannelPeriod(byte channelNumber, short channelPeriod) throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }

        try
        {
            if(!mAntReceiver.ANTSetChannelPeriod(channelNumber, channelPeriod))
            {
                throw new AntInterfaceException();
            }
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    }

    /**
     * Configure the length of time that the receiver will search for a channel
     * before timing out. Note that a value of zero will disable high priority 
     * search mode, and a value of 255 sets an infinite search time-out.
     * <p>
     * See the 
     * <a href="http://www.thisisant.com/pages/developer-zone/ant-protocol-and-usage">
     * ANT Message Protocol and Usage</a> document for details.
     * 
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * @since 1.0
     */
    public void ANTSetChannelSearchTimeout(byte channelNumber, byte searchTimeout) throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }

        try
        {
            if(!mAntReceiver.ANTSetChannelSearchTimeout(channelNumber, searchTimeout))
            {
                throw new AntInterfaceException();
            }
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    }

    /**
     * Set the RF frequency for a particular channel.
     * <p>
     * A frequency in the 2402 MHz to 2480 MHz range is recommended.
     * <p>
     * See the 
     * <a href="http://www.thisisant.com/pages/developer-zone/ant-protocol-and-usage">
     * ANT Message Protocol and Usage</a> document for details.
     * 
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * @since 1.0
     */
    public void ANTSetChannelRFFreq(byte channelNumber, byte radioFrequency) throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }

        try
        {
            if(!mAntReceiver.ANTSetChannelRFFreq(channelNumber, radioFrequency))
            {
                throw new AntInterfaceException();
            }
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    }
    
    // SetNetworkKey
    // SetTransmitPower
    
    /**
     * Add channel IDs to the inclusion/exclusion list.
     * <p>
     * See the 
     * <a href="http://www.thisisant.com/pages/developer-zone/ant-protocol-and-usage">
     * ANT Message Protocol and Usage</a> document for details.
     * 
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * 
     * @since 1.0
     */
    public void ANTAddChannelId(byte channelNumber, short deviceNumber, byte deviceType, byte txType, byte listIndex) throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }

        try
        {
            if(!mAntReceiver.ANTAddChannelId(channelNumber, deviceNumber, deviceType, txType, listIndex))
            {
                throw new AntInterfaceException();
            }
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    }
    
    /**
     * Configure the inclusion/exclusion list. The size determines which IDs in
     * the list are to be used (setting a size of 0 disables the include/
     * exclude list) and the exclude variable determines whether the IDs are to
     * be found or to be ignored when the device is searching.
     * <p>
     * See the 
     * <a href="http://www.thisisant.com/pages/developer-zone/ant-protocol-and-usage">
     * ANT Message Protocol and Usage</a> document for details.
     * 
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * 
     * @since 1.0
     */
    public void ANTConfigList(byte channelNumber, byte listSize, byte exclude) throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }

        try
        {
            if(!mAntReceiver.ANTConfigList(channelNumber, listSize, exclude))
            {
                throw new AntInterfaceException();
            }
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    }
    
    /**
     * Set the transmit power level for a specified channel.
     * <p>
     * See the 
     * <a href="http://www.thisisant.com/pages/developer-zone/ant-protocol-and-usage">
     * ANT Message Protocol and Usage</a> document for details.
     * 
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * 
     * @since 1.0
     */
    public void ANTSetChannelTxPower(byte channelNumber, byte txPower) throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }

        try
        {
            if(!mAntReceiver.ANTSetChannelTxPower(channelNumber, txPower))
            {
                throw new AntInterfaceException();
            }
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    }
    
    /**
     * Configure the duration the receiver will search for a channel in low priority mode before switching to high priority mode.
     * <p>
     * See the 
     * <a href="http://www.thisisant.com/pages/developer-zone/ant-protocol-and-usage">
     * ANT Message Protocol and Usage</a> document for details.
     * 
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * @since 1.0
     */
    public void ANTSetLowPriorityChannelSearchTimeout(byte channelNumber, byte searchTimeout) throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }

        try
        {
            if(!mAntReceiver.ANTSetLowPriorityChannelSearchTimeout(channelNumber, searchTimeout))
            {
                throw new AntInterfaceException();
            }
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    } 

    // SetSerialNumChannelId
    // RxExtMesgsEnable
    // EnableLED
    // CrystalEnable
    // LibConfig
    // ConfigFrequencyAgility
    
    /**
     * This function enables a one-time proximity requirement for searching.
     * <p>
     * Only ANT devices within the set proximity bin can be acquired. Search 
     * threshold values are not correlated to specific distances as this will 
     * be dependent to the system design.
     * <p>
     * See the 
     * <a href="http://www.thisisant.com/pages/developer-zone/ant-protocol-and-usage">
     * ANT Message Protocol and Usage</a> document for details.
     * 
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * @since 1.0
     */
    public void ANTSetProximitySearch(byte channelNumber, byte searchThreshold) throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }

        try
        {
            if(!mAntReceiver.ANTSetProximitySearch(channelNumber, searchThreshold))
            {
                throw new AntInterfaceException();
            }
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    }

    // SetChannelSearchPriority
    // SetUSBDescriptorString
    
/////////////////////////////////////////////////////////////////////////////
//    CONTROL MESSAGES
/////////////////////////////////////////////////////////////////////////////
    
    /**
     * This message is sent to the module to reset the system and put it in a 
     * known, low-power state. 
     * <p>
     * Execution of this command terminates all channels. All information 
     * previously configured in the system can no longer be considered valid.
     * <p> 
     * After a Reset System command has been issued, the application should 
     * wait 500ms to ensure that ANT is in the proper, “after-reset” state 
     * before any further commands are issued from the host.
     * <p>
     * See the 
     * <a href="http://www.thisisant.com/pages/developer-zone/ant-protocol-and-usage">
     * ANT Message Protocol and Usage</a> document for details.
     * 
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * 
     * @since 1.0
     */
    public void ANTResetSystem() throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }

        try
        {
            if(!mAntReceiver.ANTResetSystem())
            {
                throw new AntInterfaceException();
            }
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    }
    
    /**
     * Open a channel that has been previously assigned and configured.
     * <p>
     * See the 
     * <a href="http://www.thisisant.com/pages/developer-zone/ant-protocol-and-usage">
     * ANT Message Protocol and Usage</a> document for details.
     * 
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * 
     * @since 1.0
     */
    public void ANTOpenChannel(byte channelNumber) throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }

        try
        {
            if(!mAntReceiver.ANTOpenChannel(channelNumber))
            {
                throw new AntInterfaceException();
            }
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    }

    /**
     * Close a channel that has been previously opened.
     * <p>
     * The host will initially receive a RESPONSE_NO_ERROR message indicating 
     * the message was successfully received by ANT. The actual closing of the 
     * channel will be indicated by an EVENT_CHANNEL_CLOSED, and the host 
     * should wait for this message before performing any other operations on 
     * the channel.
     * <p>
     * See the 
     * <a href="http://www.thisisant.com/pages/developer-zone/ant-protocol-and-usage">
     * ANT Message Protocol and Usage</a> document for details.
     * 
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * 
     * @since 1.0
     */
    public void ANTCloseChannel(byte channelNumber) throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }

        try
        {
            if(!mAntReceiver.ANTCloseChannel(channelNumber))
            {
                throw new AntInterfaceException();
            }
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    }
    
    /**
     * Request a specific information message from the device.
     * <p>
     * See the 
     * <a href="http://www.thisisant.com/pages/developer-zone/ant-protocol-and-usage">
     * ANT Message Protocol and Usage</a> document for details.
     * 
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * 
     * @since 1.0
     */
    public void ANTRequestMessage(byte channelNumber, byte messageID) throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }

        try
        {
            if(!mAntReceiver.ANTRequestMessage(channelNumber, messageID))
            {
                throw new AntInterfaceException();
            }
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    }

    // OpenRxScanMode
    // Sleep
    
/////////////////////////////////////////////////////////////////////////////
//  DATA MESSAGES
/////////////////////////////////////////////////////////////////////////////
    
    /**
     * Send a broadcast packet on a channel.
     * <p>
     * Note, data payload for a broadcast message is 8 bytes. The host 
     * application shall always define the full 8 bytes of the data packet and 
     * set unused bytes appropriately. If an application requires less than 8 
     * bytes of data to be transmitted over the air, the remaining unused bytes
     * shall be set to a predefined “unused” value (for example, ANT+ device 
     * profiles specify unused byte values, such as 0x00 or 0xFF).
     * <p>
     * See the 
     * <a href="http://www.thisisant.com/pages/developer-zone/ant-protocol-and-usage">
     * ANT Message Protocol and Usage</a> document for details.
     * 
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * 
     * @since 1.0
     */
    public void ANTSendBroadcastData(byte channelNumber, byte[] txBuffer) throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }
        
        if(txBuffer.length != AntDefine.ANT_STANDARD_DATA_PAYLOAD_SIZE)
        {
            throw new IllegalArgumentException("The provided broadcast packet has the incorrect length. " +
                    "Broadcast packets must be " + AntDefine.ANT_STANDARD_DATA_PAYLOAD_SIZE + " bytes long.");
        }

        try
        {
            if(!mAntReceiver.ANTSendBroadcastData(channelNumber, txBuffer))
            {
                throw new AntInterfaceException();
            }
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    }

    /**
     * Send an acknowledged packet on a channel.
     * <p>
     * Note, data payload for an acknowledged message is 8 bytes. The host 
     * application shall always define the full 8 bytes of the data packet and 
     * set unused bytes appropriately. If an application requires less than 8 
     * bytes of data to be transmitted over the air, the remaining unused bytes
     * shall be set to a predefined “unused” value (for example, ANT+ device 
     * profiles specify unused byte values, such as 0x00 or 0xFF).
     * <p>
     * See the 
     * <a href="http://www.thisisant.com/pages/developer-zone/ant-protocol-and-usage">
     * ANT Message Protocol and Usage</a> document for details.
     * 
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * 
     * @since 1.0
     */
    public void ANTSendAcknowledgedData(byte channelNumber, byte[] txBuffer) throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }
        
        if(txBuffer.length != AntDefine.ANT_STANDARD_DATA_PAYLOAD_SIZE)
        {
            throw new IllegalArgumentException("The provided acknowledged packet has the incorrect length. " +
                    "Acknowledged packets must be " + AntDefine.ANT_STANDARD_DATA_PAYLOAD_SIZE + " bytes long.");
        }

        try
        {
            if(!mAntReceiver.ANTSendAcknowledgedData(channelNumber, txBuffer))
            {
                throw new AntInterfaceException();
            }
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    } 

    /**
     * Transmits the given data on a channel as a burst message.
     * <p>
     * See the 
     * <a href="http://www.thisisant.com/pages/developer-zone/ant-protocol-and-usage">
     * ANT Message Protocol and Usage</a> document for details.<br>
     * NumDataPackets is not required, as this will be calculated.
     * 
     * @return The number of bytes still to be sent (approximately).  0 if success.
     * 
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * 
     * @since 1.0
     */
    public int ANTSendBurstTransfer(byte channelNumber, byte[] txBuffer) throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }
        
        if(txBuffer.length < AntDefine.ANT_STANDARD_DATA_PAYLOAD_SIZE)
        {
            throw new IllegalArgumentException("Incorrect burst length. Bursts must be at least " +
                    AntDefine.ANT_STANDARD_DATA_PAYLOAD_SIZE + " bytes long.");
        }

        try
        {
            return mAntReceiver.ANTSendBurstTransfer(channelNumber, txBuffer);
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    }
    
    /**
     * Send a burst packet on a channel (8 bytes).
     * <p>
     * When a single packet burst is sent, it behaves identically to an 
     * acknowledged message, there are no retries associated with a single 
     * packet burst.
     * <p>
     * See the 
     * <a href="http://www.thisisant.com/pages/developer-zone/ant-protocol-and-usage">
     * ANT Message Protocol and Usage</a> document for details.
     * <p>
     * NOTE: Sending individual burst packets will most likely result in a 
     * failure due to the data not arriving at the chip quickly enough for it
     * to continue the burst.
     *
     * @param control Is the combination of sequence number and channel number.
     * @param txBuffer Must be 8 bytes of data to transmit as a bust packet.
     *
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * 
     * @since 1.0
     * 
     * @deprecated This function will be removed in a later version. Send a full burst with
     * {@link #ANTSendBurstTransfer(byte, byte[])} instead.
     */
    @Deprecated
    public void ANTSendBurstTransferPacket(byte control, byte[] txBuffer) throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }

        // Check data length
        if(txBuffer.length != AntDefine.ANT_STANDARD_DATA_PAYLOAD_SIZE)
        {
        	if(txBuffer.length > AntDefine.ANT_STANDARD_DATA_PAYLOAD_SIZE)
        	{
        		throw new IllegalArgumentException("Burst packet is too long, must be no more than" 
        				+ AntDefine.ANT_STANDARD_DATA_PAYLOAD_SIZE + " bytes long.");
        	}
        	else // too short unless last packet
        	{
        		if(isLastBurstPacket(control))
        		{
        			if(isFirstBurstPacket(control))
        			{
        				throw new IllegalArgumentException("Single packet burst has incorrect length. Single packet" +
        						" bursts must be " + AntDefine.ANT_STANDARD_DATA_PAYLOAD_SIZE + " bytes in length.");
        			}	
        		}
        		else
        		{
        			throw new IllegalArgumentException("Intermediate burst packet has incorrect length. " +
        					"Intermediate burst packets must be " + AntDefine.ANT_STANDARD_DATA_PAYLOAD_SIZE + " bytes long.");
        		}
        	}
        }

        try
        {
            if(!mAntReceiver.ANTSendBurstTransferPacket(control, txBuffer))
            {
                throw new AntInterfaceException();
            }
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    } 
    
    /**
     * Check if the control byte identifies this packet as the first in a burst.
     *
     * @param control Is the combination of sequence number and channel number.
     *
     * @return True if sequence number = first message.
     */
    private boolean isFirstBurstPacket(byte control)
    {
    	return ((byte)(control & AntDefine.SEQUENCE_NUMBER_MASK) == AntDefine.SEQUENCE_FIRST_MESSAGE);
    }

    /**
     * Check if the control byte identifies this packet as the last in a burst.
     *
     * @param control Is the combination of sequence number and channel number.
     *
     * @return True if last message flag is set.
     */
    private boolean isLastBurstPacket(byte control)
    {
    	return ((byte)(control & AntDefine.SEQUENCE_LAST_MESSAGE) > 0);
    }
    
/////////////////////////////////////////////////////////////////////////////
//  TEST MODE
/////////////////////////////////////////////////////////////////////////////
    
    // InitCWTestMode
    // SetCWTestMode
    
/////////////////////////////////////////////////////////////////////////////
//  EXTENDED DATA MESSAGES
/////////////////////////////////////////////////////////////////////////////
    
    // SendExtBroadcastData
    // SendExtAcknowledgedData
    // SendExtBurstTransferPacket
    
/////////////////////////////////////////////////////////////////////////////
//  MULTI-MODE COMM CHIP SPECIFIC MESSAGES
/////////////////////////////////////////////////////////////////////////////
    
    // SetNetworkKey128
    
    /**
     * ANT config event buffering.
     * <p>
     * See the <a href="http://www.thisisant.com/pages/developer-zone/ant-protocol-and-usage">ANT Message Protocol and Usage</a> document for details.
     * 
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * 
     * @since 1.3
     */
    public void ANTConfigEventBuffering(short screenOnFlushTimerInterval, short screenOnFlushBufferThreshold, short screenOffFlushTimerInterval, short screenOffFlushBufferThreshold) throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }

        try
        {
            if(!mAntReceiver.ANTConfigEventBuffering(screenOnFlushTimerInterval, screenOnFlushBufferThreshold, screenOffFlushTimerInterval, screenOffFlushBufferThreshold))
            {
                throw new AntInterfaceException();
            }
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    }

    /**
     * ANT disable event buffering.
     * <p>
     * See the <a href="http://www.thisisant.com/pages/developer-zone/ant-protocol-and-usage">ANT Message Protocol and Usage</a> document for details.
     * 
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * 
     * @since 1.1
     */
    public void ANTDisableEventBuffering() throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }

        try
        {
            if(!mAntReceiver.ANTDisableEventBuffering())
            {
                throw new AntInterfaceException();
            }
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    }
    
/////////////////////////////////////////////////////////////////////////////
//  ANT RADIO SERVICE RELATED FUNCTIONS
/////////////////////////////////////////////////////////////////////////////
    
    /**
     * Returns the version code (eg. 1) of ANTLib used by the ANT Radio Service
     *
     * @return the service library version code, or 0 on error.
     * 
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * 
     * @since 1.2
     */
    public int getServiceLibraryVersionCode()  throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }

        if(mServiceLibraryVersionCode == 0)
        {
            try
            {
                mServiceLibraryVersionCode = mAntReceiver.getServiceLibraryVersionCode();
            }
            catch(RemoteException e)
            {
                throw new AntRemoteException(e);
            }
        }

        return mServiceLibraryVersionCode;
    }

    /**
     * Returns the version name (eg "1.0") of ANTLib used by the ANT Radio Service
     *
     * @return the service library version name, or null on error.
     * 
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * 
     * @since 1.2
     */
    public String getServiceLibraryVersionName()  throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }

        try
        {
            return mAntReceiver.getServiceLibraryVersionName();
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    }

    /**
     * Take control of the ANT Radio.
     * 
     * @return True if control has been granted, false if another application has control or the request failed.
     * 
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * 
     * @since 1.5
     */
    public boolean claimInterface() throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }

        try
        {
            return mAntReceiver.claimInterface();
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    }

    /**
     * Give up control of the ANT Radio.
     * 
     * @return True if control has been given up, false if this application did not have control.
     * 
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * 
     * @since 1.5
     */
    public boolean releaseInterface() throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }

        try
        {
            return mAntReceiver.releaseInterface();
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    }

    /**
     * Claims the interface if it is available.  If not the user will be prompted (on the notification bar) if a force claim should be done.
     * If the ANT Interface is claimed, an AntInterfaceIntent.ANT_INTERFACE_CLAIMED_ACTION intent will be sent, with the current applications pid.
     * 
     * @param appName The name if this application, to show to the user.
     * 
     * @return false if a claim interface request notification already exists.
     * 
     * @throws IllegalArgumentException
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * 
     * @since 1.5
     */
    public boolean requestForceClaimInterface(String appName) throws AntInterfaceException
    {
        if((null == appName) || (appName.equals("")))
        {
            throw new IllegalArgumentException("Application name not specified");
        }

        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }

        try
        {
            return mAntReceiver.requestForceClaimInterface(appName);
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    }
    
    /**
     * Clears the notification asking the user if they would like to seize control of the ANT Radio.
     * 
     * @return false if this process is not requesting to claim the interface.
     * 
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * 
     * @since 1.5
     */
    public boolean stopRequestForceClaimInterface() throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }

        try
        {
            return mAntReceiver.stopRequestForceClaimInterface();
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    }

    /**
     * Check if the calling application has control of the ANT Radio.
     * 
     * @return True if control is currently granted.
     * 
     * @throws AntInterfaceException
     * @throws AntServiceNotConnectedException
     * @throws AntRemoteException
     * 
     * @since 1.5
     */
    public boolean hasClaimedInterface() throws AntInterfaceException
    {
        if(!mServiceConnected)
        {
            throw new AntServiceNotConnectedException();
        }

        try
        {
            return mAntReceiver.hasClaimedInterface();
        }
        catch(RemoteException e)
        {
            throw new AntRemoteException(e);
        }
    }

    /**
     * Check if this device has support for ANT.
     * 
     * @return True if the device supports ANT (may still require ANT Radio 
     * Service be installed).  The result is dynamic as services providing the
     * back-end ANT support (hardware interface) are added or removed.
     * 
     * @since 1.5
     */
    public static boolean hasAntSupport(Context pContext)
    {
        PackageManager packageManager = pContext.getPackageManager();
        boolean antSupported = false;
        
        antSupported = Arrays.asList(packageManager.getSystemSharedLibraryNames()).contains(ANT_LIBRARY_NAME);
        
        List<ResolveInfo> resolveInfoList = packageManager.queryIntentServices(new Intent(INTENT_ACTION_QUERY_SERVICE_INFO), 0);
        
        if (!resolveInfoList.isEmpty()) 
        {
            antSupported = true;
        }

        return antSupported;
    }
}
